package edu.isistan.matching.utils.serviceMatching;

import iglu.ir.TermVector;

import java.util.Hashtable;
import java.util.Iterator;

import edu.isistan.matching.scoreTypes.ComposeMatchScore;
import edu.isistan.matching.scoreTypes.MatchScore;
import edu.isistan.matching.scoreTypes.ScoreMatrix;
import edu.isistan.matching.utils.KuhnsHungarianMethod;
import edu.isistan.matching.utils.semanticLexicon.ISemanticLexicon;
import edu.isistan.matching.utils.semanticLexicon.WordAnalyzer;
import edu.isistan.metamodel.DataType;
import edu.isistan.metamodel.Operation;



/**
 * @author Matias
 * 
 */
public class SemanticMatching extends AbstractMatching {

	public static double IDEM_MEASURE = 10;

	public static double SYNONYMS_MEASURE = 8;

	public static double HYPERNYMS_MEASURE = 6;

	public static double HYPONYMS_MEASURE = 6;

	public static double NO_SIMILARITY = 0;

	public SemanticMatching() {
		super();
		setOperationNamesWeigth(0.65);
		setOperationMessagesWeigth(0.35);
	}

	private Hashtable<String, Double> cache = new Hashtable<String, Double>();
	private static String STRING_JOINER = "<->";

	@Override
	public double analyzeOperationMatching(Operation op1, Operation op2) {
		return matchDocumentTerms(op1.getName(), op2.getName());
	}

	@Override
	public double analyzePrimitivesMatching(DataType tipo1, DataType tipo2) {
		return matchDocumentTerms(tipo1.getName(), tipo2.getName());
	}

	private ISemanticLexicon wordAnalyzer = new WordAnalyzer();

	public ISemanticLexicon getWordAnalyzer() {
		return wordAnalyzer;
	}

	public void setWordAnalyzer(ISemanticLexicon wordAnalyzer) {
		this.wordAnalyzer = wordAnalyzer;
	}

	protected double matchDocumentTerms(String sourceTerm, String targetTerm) {
		getLogger().debug("In MatchDocumentTerms");
		getLogger().debug("comparando: " + sourceTerm + "<->" + targetTerm);
		double score = analizarTerminos(sourceTerm, targetTerm);
		getLogger().debug("score: " + score);
		return score;
	}

	/**
	 * Dados dos Strings pasados por parámetos, en primer lugar se Preprocesa
	 * cada uno, con el fin de obtener los términos que componen cada uno de
	 * ellos (Este preporcesamiento incluye remover Stop Words y llevar las
	 * palabras a su raiz). Luego, se busca la mejor correspondencia entre los
	 * terminos obtenidos en la tarea previamente mencionada. Para ello, se
	 * compara el grado de similitud entre los términos (se busca saber si
	 * ambos términos son iguales, sinónimos, hipónimos o hiperónimos)
	 * 
	 * @return grado de similitud entre strings.
	 */
	private double analizarTerminos(String sourceTerm, String targetTerm) {
		Double resultadoGuardado = cache.get(sourceTerm + STRING_JOINER + targetTerm);
		if (resultadoGuardado != null) {
			getLogger().debug("Devolviendo resultado de la cache de " + sourceTerm + "<->" + targetTerm + " valor " + resultadoGuardado);
			return resultadoGuardado;
		}
		resultadoGuardado = cache.get(targetTerm + STRING_JOINER + sourceTerm);
		if (resultadoGuardado != null) {
			getLogger().debug("Devolviendo resultado de la cache de " + targetTerm + "<->" + sourceTerm + " valor " + resultadoGuardado);
			return resultadoGuardado;
		}

		TermVector terminosSource = getTerms(sourceTerm);
		TermVector terminosTarget = getTerms(targetTerm);
		getLogger().debug("--->VSource: " + terminosSource);
		getLogger().debug("--->VTarget: " + terminosTarget);
		int m = terminosSource.size();
		int n = terminosTarget.size();
		getLogger().debug("MATCHING TERMS: M: " + m + " N: " + n);
		ScoreMatrix scoreMatrix = new ScoreMatrix(m, n);
		String[] arraySource = termVector2Array(terminosSource);
		String[] arrayTarget = termVector2Array(terminosTarget);
		for (int i = 0; i < arraySource.length; i++) {
			for (int j = 0; j < arrayTarget.length; j++) {
				String source = arraySource[i];
				String target = arrayTarget[j];
				double compValue = compararTerminos(source, target);
				scoreMatrix.addValue(i, j, new MatchScore(compValue));
			}
		}
		ComposeMatchScore matchResult = new ComposeMatchScore();
		matchResult.setChildScoreMatrix(scoreMatrix);
		KuhnsHungarianMethod.findAssigment(matchResult, true);
		if (cache.get(sourceTerm + STRING_JOINER + targetTerm) == null && cache.get(targetTerm + STRING_JOINER + sourceTerm) == null) {
			cache.put(sourceTerm + STRING_JOINER + targetTerm, matchResult.getScore());
		}
		return matchResult.getScore();
	}

	/**
	 * Dado dos String devuelve el valor de su similitud. Se compara si son
	 * iguales ambos términos (En caso que lo sean, se devuelve el valor
	 * máximo). En caso que no sean iguales, se verifica que sean
	 * sinónimos.(En este caso se asigna un 80% del máximo valor) Si ambos
	 * términos no son iguales, se compara si uno de los terminos es
	 * hiperónimo o hipónimo del otro. (En caso que lo sean se le asigna
	 * el 60% del valor máximo dividido por la distancia que hay entre ambos
	 * términos en la estructura de hiperónimo o hipónimo) En caso que
	 * no se cumpla ninguna relacion entre ambas palabras se devuelve una
	 * constante que representa el valor minimo.
	 * 
	 * @param term1
	 *            Primer Término a comparar
	 * @param term2
	 *            Segundo Término a comparar
	 * @return
	 */
	private double compararTerminos(String term1, String term2) {

		Double resultadoGuardado = cache.get(term1 + STRING_JOINER + term2);
		if (resultadoGuardado != null) {
			getLogger().debug("Devolviendo resultado de la cache de " + term1 + "<->" + term2 + " valor " + resultadoGuardado);
			return resultadoGuardado;
		}
		resultadoGuardado = cache.get(term2 + STRING_JOINER + term1);
		if (resultadoGuardado != null) {
			getLogger().debug("Devolviendo resultado de la cache de " + term2 + "<->" + term1 + " valor " + resultadoGuardado);
			return resultadoGuardado;
		}

		getLogger().debug("Subcomparando: " + term1 + "<->" + term2);

		if (term1.equalsIgnoreCase(term2) || getWordAnalyzer().areSynonyms(term1, term2)) {
			getLogger().debug("->Son  Iguales!");
			if (cache.get(term1 + STRING_JOINER + term2) == null && cache.get(term2 + STRING_JOINER + term1) == null) {
				cache.put(term1 + STRING_JOINER + term2, IDEM_MEASURE);
			}

			return IDEM_MEASURE;
		}

		if (getWordAnalyzer().areSynonyms(term1, term2)) {
			getLogger().debug("->Son  Sinonimos!");
			if (cache.get(term1 + STRING_JOINER + term2) == null && cache.get(term2 + STRING_JOINER + term1) == null) {
				cache.put(term1 + STRING_JOINER + term2, SYNONYMS_MEASURE);
			}

			return SYNONYMS_MEASURE;
		}
		int levelHypernyms = getWordAnalyzer().areHypernyms(term1, term2);
		if (levelHypernyms != ISemanticLexicon.NO_RELATION) {
			int score = (new Double(HYPERNYMS_MEASURE / levelHypernyms)).intValue();
			getLogger().debug("->Son Hiper�nimos, valor:" + score);
			if (cache.get(term1 + STRING_JOINER + term2) == null && cache.get(term2 + STRING_JOINER + term1) == null) {
				cache.put(term1 + STRING_JOINER + term2, new Double(score));
			}
			return score;
		}
		int levelHyponyms = getWordAnalyzer().areHyponyms(term1, term2);
		if (levelHyponyms != ISemanticLexicon.NO_RELATION) {
			int score = (new Double(HYPONYMS_MEASURE / levelHyponyms)).intValue();
			getLogger().debug("->Son Hop�nimos, valor:" + score);
			if (cache.get(term1 + STRING_JOINER + term2) == null && cache.get(term2 + STRING_JOINER + term1) == null) {
				cache.put(term1 + STRING_JOINER + term2, new Double(score));
			}
			return score;
		}
		if (cache.get(term1 + STRING_JOINER + term2) == null && cache.get(term2 + STRING_JOINER + term1) == null) {
			cache.put(term1 + STRING_JOINER + term2, new Double(NO_SIMILARITY));
		}
		return NO_SIMILARITY;
	};

	/**
	 * Trasforma el TermVector pasado por parámetro en un array de String, el
	 * cual contiene los términos de dicho vector
	 * 
	 * @param vector
	 *            Vector a trasformar
	 * @return@SuppressWarnings("unchecked")
 array de string de términos
	 */
	private String[] termVector2Array(TermVector vector) {
		String[] array = new String[vector.size()];
		int pos = 0;
		for (Iterator iterator = vector.termIterator(); iterator.hasNext();) {
			String term = (String) iterator.next();
			array[pos] = term;
			pos++;
		}
		return array;
	}

	/**
	 * Devuelve un Term vector con el resultado de preprocesar el String pasado
	 * por parámetro
	 */
	private TermVector getTerms(String text) {
		SemanticPreprocessor preprocessor = new SemanticPreprocessor();
		TermVector terminos = preprocessor.preprocess(text);
		if (terminos == null || terminos.size() == 0) {
			terminos = new TermVector();
			terminos.increment(text);
		}
		return terminos;
	}
	
}